package merlin.managers;

import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.sql.Connection;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import merlin.beans.CustomerDetailsBean;
import merlin.beans.DirectorDetailsBean;
import merlin.dao.ConnectionPool;
import merlin.dao.DirectorDetailsDAO;
import merlin.dao.LookupDetailsDAO;
import merlin.utils.ApplicationResources;
import merlin.utils.MailSender;
import merlin.utils.MerlinUtilities;

public class BibServiceMailingListGenerator implements Callable<Boolean> {

	private CustomerDetailsBean customerDetailsBean;
	private List<String> companyIds; 
	private int addressType;
	private int[] directorSortOptions;
	private String reportNamesUsed;

	// director sort options.

	private static final int SORT_OPTION_DIRECTOR_WITH_LARGEST_SHARES = 1;
	private static final int SORT_OPTION_DIRECTOR_WITH_SHARES = 2;
	private static final int SORT_OPTION_LEVEL_1_DIRECTOR = 3;
	private static final int SORT_OPTION_LEVEL_2_DIRECTOR = 4;
	private static final int SORT_OPTION_MANAGING_DIRECTOR_DEFAULT = 5;
	private static final int SORT_OPTION_OLDEST_DIRECTOR = 6;
	private static final int SORT_OPTION_YOUNGEST_DIRECTOR = 7;

	private static final int ADDRESS_TYPE_DIRECTOR_HOME_ADDRESS = 1;
	private static final int ADDRESS_TYPE_REGISTERED_OFFICE_ADDRESS = 2;
	private static final int ADDRESS_TYPE_TRADING_ADDRESS = 3;



	public BibServiceMailingListGenerator(CustomerDetailsBean customerDetailsBean, String[] companyIds, int addressType, int[] directorSortOptions, String reportNamesUsed){
		this.customerDetailsBean = customerDetailsBean;
		// simply using the List returned by Arrays.asList() doesn't work as List returned from that method doesn't have an iterator
		// that can remove elements from the collection - required to eliminate the companies that are part of the exclusion list, 
		// and companies that are in financial distress.
		this.companyIds = new LinkedList<String>(Arrays.asList(companyIds)); 
		this.addressType = addressType;
		this.directorSortOptions = directorSortOptions;
		this.reportNamesUsed = MerlinUtilities.capitalizeFully(reportNamesUsed.toLowerCase());
	}

	public Boolean call() throws Exception {
		ConnectionPool poolResearcher = null;
		Connection conResearcher = null;

		try{


			String whereClause = "DIRECTOR_FIRST_NAME is not null";
			String orderByClause = "", sortOrderTextForEmailBody = "\nSort order employed:\n";

			// whether to check if the sort order has been properly applied while selecting the directors.
			boolean sortCheckToBeApplied = false;

			for(int i=0;i<directorSortOptions.length;i++){
				String orderByToAdd = null;
				switch(directorSortOptions[i]){
				case SORT_OPTION_DIRECTOR_WITH_LARGEST_SHARES:
					orderByToAdd = "DIRECTOR_SHAREHOLDER1 DESC";
					sortOrderTextForEmailBody += (i+1) + ". Director with largest number of shares\n";
					break;
				case SORT_OPTION_DIRECTOR_WITH_SHARES:
					orderByToAdd = "DIRECTOR_SHAREHOLDER2 DESC";
					sortOrderTextForEmailBody += (i+1) + ". Director with shares\n";
					break;
				case SORT_OPTION_LEVEL_1_DIRECTOR:
					orderByToAdd = "DIRECTOR_LEVEL1 DESC";
					sortOrderTextForEmailBody += (i+1) + ". Level 1 Director (CEO, MD, President etc)\n";
					break;
				case SORT_OPTION_LEVEL_2_DIRECTOR:
					orderByToAdd = "DIRECTOR_LEVEL2 DESC";
					sortOrderTextForEmailBody += (i+1) + ". Level 2 Director (FD,SD, VP)\n";
					break;
				case SORT_OPTION_OLDEST_DIRECTOR:
					orderByToAdd = "DIRECTOR_DATE_OF_BIRTH";
					sortOrderTextForEmailBody += (i+1) + ". Oldest Named Director\n";
					break;
				case SORT_OPTION_YOUNGEST_DIRECTOR:
					orderByToAdd = "DIRECTOR_DATE_OF_BIRTH DESC";
					sortOrderTextForEmailBody += (i+1) + ". Youngest Named Director\n";
					break;
				case SORT_OPTION_MANAGING_DIRECTOR_DEFAULT:
					sortCheckToBeApplied = true;
					sortOrderTextForEmailBody += (i+1) + ". \"Managing Director\" default\n";
					break;
				}
				if(orderByToAdd != null)orderByClause = addWithSeparator(orderByClause, orderByToAdd, ",");
			}

			String mailSubject = null;
			boolean emitTelephoneDetails = true;
			switch(addressType){
			case ADDRESS_TYPE_DIRECTOR_HOME_ADDRESS:
				emitTelephoneDetails = false; // the home address table doesn't have the telephone  
				mailSubject = "Director Home Address Mailing List";
				break;
			case ADDRESS_TYPE_REGISTERED_OFFICE_ADDRESS:
				mailSubject = "Director Registered Address Mailing List";
				break;
			case ADDRESS_TYPE_TRADING_ADDRESS:
				mailSubject = "Director Trading Address Mailing List";
				break;
			}
			
			// store the ids of the directors fetched so far so that there is no duplicate directors in the list.
			Set<Integer> directorIds = new HashSet<Integer>();

			SimpleDateFormat sdf = new SimpleDateFormat("dd_MMM_yyyy_hh_mm_ss_a");
			String time = sdf.format(new java.util.Date());
			File file = new File(ApplicationResources.getInstance().getProperty("iwSummaryPDFStorageFolder") + "BIBMailingList-" + time + ".txt");
			System.out.println("Saving records to file " + file);
			OutputStream bOut = new FileOutputStream(file);
			BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(bOut));

			DirectorDetailsBean headerBean = new DirectorDetailsBean("COMPANY NUMBER","TITLE","FIRST NAME","SURNAME","POSITION","COMPANY NAME","ADDRESS1","ADDRESS2","ADDRESS3","ADDRESS4","POSTCODE","TELEPHONE");
			saveDataToWriter(headerBean, emitTelephoneDetails, writer);
			writer.write("\n");
			
			poolResearcher = ConnectionPool.getConnectionPool("researcher");
			conResearcher = poolResearcher.getConnection();

			DirectorDetailsDAO directorDao = new DirectorDetailsDAO(conResearcher);
			LookupDetailsDAO lookupDao = new LookupDetailsDAO(conResearcher);

			// remove the companies that are in the exclusion list for this customer.
			if(customerDetailsBean.getCompanyNumber() != null && customerDetailsBean.getCompanyNumber().trim().length() > 0){
				companyIds.removeAll(lookupDao.getCompanyIdsInBibMailingListExclusionsList(customerDetailsBean.getCompanyNumber()));
			}
			
			// remove companies that are in financial distress.
			companyIds.removeAll(lookupDao.selectCompaniesInFinancialDistress(companyIds));
			
			// Step 1: Sort company ids in ascending order.
			Collections.sort(companyIds);
			
			for(String companyId : companyIds){
				DirectorDetailsBean bean =  directorDao.getDirectorByCompanyNumberForBIBService(companyId, whereClause, orderByClause);
				if(bean == null)continue; // no director found for this company. go to next company.
				if(directorIds.contains(bean.getDirectorId()))continue;	// duplicate director, skip.
				// new director, process it.
				if(bean.getPosition()==null){
					bean.setPosition("DIRECTOR"); // default position name.
				}
				directorIds.add(bean.getDirectorId());
				if(sortCheckToBeApplied){
					// determine if the record needs to be cleaned if none of the sorting columns were used.
					boolean sortAffected = false;

					for(int i=0;i<directorSortOptions.length;i++){
						switch(directorSortOptions[i]){
						case SORT_OPTION_DIRECTOR_WITH_LARGEST_SHARES:
							sortAffected = (bean.getShareholder1() > 0);
							break;
						case SORT_OPTION_DIRECTOR_WITH_SHARES:
							sortAffected = (bean.getShareholder2() > 0);
							break;
						case SORT_OPTION_LEVEL_1_DIRECTOR:
							sortAffected = (bean.getLevel1() == 1);
							break;
						case SORT_OPTION_LEVEL_2_DIRECTOR:
							sortAffected = (bean.getLevel2() == 1);
							break;
						case SORT_OPTION_OLDEST_DIRECTOR:
							sortAffected = (bean.getDateOfBirth() != null);
							break;
						case SORT_OPTION_YOUNGEST_DIRECTOR:
							sortAffected = (bean.getDateOfBirth() != null);
							break;
						}
					}
					if(!sortAffected){
						bean.setPrefix(null);
						bean.setFirstname(null);
						bean.setSurname(null);
						bean.setServiceAddressId(0);
						bean.setPosition("THE MANAGING DIRECTOR");
					}
				}
				// get the address details.
				bean.setCompanyName(lookupDao.selectCompanyNameByPK(companyId));
				
				switch(addressType){
				case ADDRESS_TYPE_DIRECTOR_HOME_ADDRESS:
					bean = directorDao.getHomeAddress(bean);
					break;
				case ADDRESS_TYPE_REGISTERED_OFFICE_ADDRESS:
					bean = directorDao.getRegisteredOfficeAddress(bean);
					break;
				case ADDRESS_TYPE_TRADING_ADDRESS:
					bean = directorDao.getTradingAddress(bean);
					break;
				}
				// save to file.
				saveDataToWriter(bean, emitTelephoneDetails, writer);
			}

			writer.close();
			bOut.close();

			// create the zipped data for email attachment.
			ByteArrayOutputStream zipOut = new ByteArrayOutputStream();
			ZipOutputStream out = new ZipOutputStream(zipOut);
			byte[] data = new byte[1000]; 
			BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
			int count;
			out.putNextEntry(new ZipEntry("BIBMailingList-" + time + ".txt"));
			while((count = in.read(data,0,1000)) != -1){  
				out.write(data, 0, count);
			}
			in.close();
			out.flush();
			out.close(); 

			file.delete(); // delete the intermediate text file.
			
			// send email.
			String emailBody = "Mailing list based on " + reportNamesUsed + " reports\n";
			emailBody += sortOrderTextForEmailBody;
			emailBody += "\n\nA total of " + directorIds.size() + " records.";
			
			//System.out.println(emailBody);
			
			// send to end user. 
			MailSender.sendMail("Niel.Dalrymple@industrywatch.co.uk", customerDetailsBean.getEmailAddress(), mailSubject, emailBody, "text/plain", zipOut.toByteArray(), "application/zip", "BIBMailingList-" + time + ".zip");

			// send a copy to MSAL without the attachment.
			MailSender.sendMail("Niel.Dalrymple@industrywatch.co.uk", "Niel.Dalrymple@industrywatch.co.uk", mailSubject, emailBody, "text/plain", null, null, null);

		}catch(Exception e){
			e.printStackTrace();
		}finally{
			poolResearcher.returnConnection(conResearcher);
			conResearcher = null;
		}
		return Boolean.TRUE;
	}

	private String addWithSeparator(String base, String toAdd, String separator){
		if(base == null || base.length() == 0){
			base = toAdd;
		}else{
			base += " " + separator + " " + toAdd;
		}

		return base;
	}

	private void saveDataToWriter(DirectorDetailsBean bean, boolean emitTelephoneDetails, Writer out)throws Exception{
		StringBuilder builder = new StringBuilder();
		builder.append(convertString(bean.getCompanyNumber())).append('\t');
		builder.append(convertString(bean.getPrefix())).append('\t');
		builder.append(convertString(bean.getFirstname())).append('\t');
		builder.append(convertString(bean.getSurname())).append('\t');
		builder.append(convertString(bean.getPosition())).append('\t');
		builder.append(convertString(bean.getCompanyName())).append('\t');
		builder.append(convertString(bean.getAddress1())).append('\t');
		builder.append(convertString(bean.getAddress2())).append('\t');
		builder.append(convertString(bean.getAddress3())).append('\t');
		builder.append(convertString(bean.getAddress4())).append('\t');
		builder.append(convertString(bean.getPostcode()));
		if(emitTelephoneDetails){
			builder.append('\t').append(convertString(bean.getTelephone()));
		}
		builder.append('\n');
		System.out.println(builder);
		out.write(builder.toString());
		out.flush();
	}
	
	private String convertString(String input){
		if(input == null || input.length() == 0){
			return "";
		}else{
			return input.toUpperCase();
		}
	}
}
